package wcg;


import ikelib.Crc;
import ikelib.byteUtil;
import ikelib.unsigned_byte;

import java.awt.Frame;
import java.io.*;
import java.net.*;
import java.util.*;

//import org.omg.CORBA.portable.InputStream;


public class WinReceiver extends Thread  {
	
	private DatagramSocket socket = null;
	private UdpCallBack Parent = null;  //  @jve:decl-index=0:
	public boolean  Terminate = false;
	public boolean  ACTAckEnable=true;
	private long ActSeqOld=0;
	private HashMap<InetAddress,Integer> SeqManage = new HashMap<InetAddress,Integer>();  //  @jve:decl-index=0:
	public int printMessageControl=0;
	public static final int showSeqError=0x1; // シーケンスエラー時に表示する
	public static final int showProtocolError=0x2; // プロトコルエラー時に表示する
	public static final int showAckOrNack=0x4; // ACK,NACK送信時に表示する
	
	//-- ACT Controlクラス
	class ActControl{
		private int AckunitMask;
		private int Ackunit;
		private long SendCounterLong;
		public long ReceiveSeq;// 最後に受信したSEQ
		
		private static final int MaxAck=32;
		private TreeSet<Long> Seq;
	
		public ActControl(){
			Seq = new TreeSet<Long>();
			setAckUnit(8);
			SendCounterLong = 0L;

		}
		public void incSendCounter(){
			SendCounterLong++;
		}
		public void setAckUnit(int ackunit){
			Ackunit = ackunit;
			AckunitMask = 0;
			// ACKのビットマップ作成のためのマスクを作成
			for(int i=0;i<ackunit;i++){
				AckunitMask = AckunitMask<<1 | 0x1;
			}
		}
		// seq番号を保持してゆく
		public void putSeq(long sq){
			Seq.add(sq);
		}
	
		public int getSeqNum(){
			return Seq.size();
		}
		// 保存されたseq番号をスキャンして桁上がりの無い範囲でのseq番号を取得する。
		// 取得後は削除される。
		public TreeSet<Long> getSeq(){
			if(Seq.size()==0) return null;
			
			TreeSet<Long> ret = new TreeSet<Long>();
			int count=0;
			Long fsq = Seq.first();
			Iterator<Long> ite = Seq.iterator();
			while(ite.hasNext()){
				Long sq = ite.next();
				//if((fsq & (0xffffffffffffffffL^(long)AckunitMask)) != (sq & (0xffffffffffffffffL^(long)AckunitMask))) break;
				if(sq - fsq>=Ackunit) break;
				count++;
				ret.add(sq);
			}
			ite=ret.iterator();
			while(ite.hasNext()){
				long l = ite.next();
				Seq.remove(l);
			}
			return ret;
		}
	}
	// ------------
	private HashMap<InetAddress,ActControl> SeqManageAct = new HashMap<InetAddress,ActControl>();  //  @jve:decl-index=0:
	
	//private	java.util.List<DatagramPacket> List =  Collections.synchronizedList(new LinkedList<DatagramPacket>());
	public static final	int		WIN_A8_HEADER = -18;	// win A8
	public static final	int		WIN_A9_HEADER = -19;	// win A9
	public static final	int		IRREGALHEADER = -1;
	public static final	int		IRREGALLENGTH = -2;
	public static final	int		IRREGALLSEQUENCE = -3;
	public static final int		CRCERROR	= -4;
	public static final	int		NOTSUPORTEDTYPE = 1;
	public static final	int		TIMEOUT = 1;
	public static final	int		NOERROR = 0;
	
	public class RecvMonitor{
		public int 			winA0UdpRecvCounter;
		public int 			winA0UdpSendRetryCounter;
		public int 			winA0UdpRetryPacketRecvCounter;
		public int 			winA0UdpRetryAbortCounter;
		public int 			winA0UdpRecvErrorCounter;
		public int 			actRecvErrorCounter;
		public int 			actRecvCounter;
		public int 			actSendAckCounter;
		public RecvMonitor(){
			winA0UdpRecvCounter=0;
			winA0UdpSendRetryCounter = 0;
			winA0UdpRetryPacketRecvCounter=0;
			winA0UdpRetryAbortCounter=0;
			winA0UdpRecvErrorCounter=0;
			actRecvErrorCounter=0;
			actRecvCounter=0;
			actSendAckCounter=0;
		}
		public String getMonitorString(){
			String ret=new String();
			ret += String.format("win A0 : Recv count        = %d\n", winA0UdpRecvCounter);
			ret += String.format("win A0 : Recv error count  = %d\n", winA0UdpRecvErrorCounter);
			ret += String.format("win A0 : Rerty packet count= %d\n", winA0UdpRetryPacketRecvCounter);
			ret += String.format("win A0 : Retry abort count = %d\n", winA0UdpRetryAbortCounter);
			ret += String.format("win A0 : Send retry count  = %d\n", winA0UdpSendRetryCounter);
			ret += String.format("ACT    : Recv count        = %d\n", actRecvCounter);
			ret += String.format("ACT    : Recv error count  = %d\n", actRecvErrorCounter);
			ret += String.format("ACT    : Send Ack count    = %d\n", actSendAckCounter);
			return ret;
		}
	}
	public RecvMonitor		monitor = new  RecvMonitor();

	private Crc CrcActRx;
	private Crc CrcActTx;
	
	public WinReceiver(DatagramSocket soc){
  		socket	=	soc;
		//CrcActRx = new Crc(0x100b,true); // ACT protocol
		//CrcActTx = new Crc(0x100b,true); // ACT protocol
		// ACTでのCRCは右送り、初期値０、のようだ。。。
  		CrcActRx = new Crc(0xd008,false); // ACT protocol
		CrcActTx = new Crc(0xd008,false); // ACT protocol
	}
	
	
	public void addRecieveListener(UdpCallBack c){
		Parent = c;
	}
	//
	//
	//
	private int checkPacket(DatagramPacket pac){
		
		byte[] data = pac.getData();
		// ACTプロトコルヘッダかな 0x31415926 
		if(ActProtocolFrame.isActHeader(data,0)){
			return checkACTPacket(pac);
		}
		// A0パケットかな？
		else if(byteUtil.convShort(data[2]) == 0xa0){
			return checkA0Packet(pac);	
		}
		else if(byteUtil.convShort(data[2]) == 0xa8){
			return WIN_A8_HEADER;	
		}
		else if(byteUtil.convShort(data[2]) == 0xa9){
			return WIN_A9_HEADER;	
		}
		return IRREGALHEADER;	// header is not A0
		
	}
	// ACT ACK送信処理
	private void sendACTAck(InetAddress adr,int port,TreeSet<Long> ack,ActControl act){
		
		byte[] header = new byte[30+2];
		ActProtocolFrame.setActHeader(header,0);
		ActProtocolFrame.setSeq(header,0,act.SendCounterLong);
		ActProtocolFrame.setAckSize(header,0,act.Ackunit);
		ActProtocolFrame.setDataType(header,0,0x0006);
		ActProtocolFrame.setDataLength(header,0,12);
		
		ActProtocolFrame.setAckSizeAndBitmap(header,0,ack);

		int crc16 = (int)(0xffffL & CrcActTx.calc(header, 30));
		ActProtocolFrame.setDataCRC(header,0,crc16);

		if(!ACTAckEnable) return;
		DatagramPacket resend = new DatagramPacket(header, header.length,adr,port);
		try {
			socket.send(resend);
			monitor.actSendAckCounter++;
			if(0!=(showAckOrNack & printMessageControl)) System.out.println(String.format("WinReceiver:Send ACT Ack seq=%d bitmap=%xH",ActProtocolFrame.getAckBase(header,0),ActProtocolFrame.getAckBitmap(header,0)));

		//	wait(10);
		} catch (IOException e) {
			e.printStackTrace();
		} 
//		catch (InterruptedException e) {
//		}		
		
		
	}
	// ACT ACK送信チェック処理
	private void checkACTAck(DatagramPacket pac,ActControl act){
		while(true){
			if(act.Ackunit > act.getSeqNum()) 	return ;
			TreeSet<Long> ack = act.getSeq();
			if(ack==null) return;
			sendACTAck(pac.getAddress(),pac.getPort(),ack,act);
		}
	}
	// ACTパケット受信処理
	private int checkACTPacket(DatagramPacket pac){
		byte[] data = pac.getData();
		//System.out.println(String.format("Rcv data %02x %02x %02x %02x %02x %02x %02x %02x",data[4],data[5],data[6],data[7],data[9],data[9],data[10],data[11]));
		
		// データ長が正しいか？
		int packlen = unsigned_byte.convShort(data[16]) * 0x100 + unsigned_byte.convShort(data[17]);
		int framelen = packlen  + 16+2+2;
		int datalen = pac.getLength();
		if(framelen != datalen){
			monitor.actRecvErrorCounter++;
			if(0!=(showProtocolError & printMessageControl)) System.out.println(String.format("WinReceiver:Rcv ACT Error data length framelen=%d datalen=%d",framelen,datalen));
			return IRREGALLENGTH;	// illegal length;
		}
		///////////////////////////////////////////////////////////////////////////
		int ccrc = (int)(0xffff & CrcActRx.calc(data,datalen-2));
		int rcrc = unsigned_byte.convShort(data[datalen-2])*0x100 + unsigned_byte.convShort(data[datalen-1]);
		if(ccrc != rcrc){
			monitor.actRecvErrorCounter++;
			if(0!=(showProtocolError & printMessageControl)) System.out.println(String.format("WinReceiver:Rcv ACT Error CRC recv=%x calc=%x",rcrc,ccrc));
			return CRCERROR;
		}
		///////////////////////////////////////////////////////////////////////////
		int datatype = unsigned_byte.convShort(data[14]) * 0x100 + unsigned_byte.convShort(data[15]);
		if(datatype != 0x00a0) {
			monitor.actRecvErrorCounter++;
			if(0!=(showProtocolError & printMessageControl)) System.out.println(String.format("WinReceiver:Rcv ACT Unknown frame type=%xH",datatype));
			return NOTSUPORTEDTYPE;
		}
		
		long seq 	= ActProtocolFrame.getSeq(data,0);
		int ackunit = ActProtocolFrame.getAckSize(data,0);
		
		monitor.actRecvCounter++;
		InetAddress adr = pac.getAddress();
		if(!SeqManageAct.containsKey(adr)){
			ActControl act = new ActControl();
			act.putSeq(seq);
			act.setAckUnit(ackunit);
			act.ReceiveSeq=seq;
			SeqManageAct.put(adr,act);
		}
		else{
			ActControl act = SeqManageAct.get(adr);
			act.putSeq(seq);
			act.setAckUnit(ackunit);
			if(++act.ReceiveSeq != seq){
				if(0!=(showSeqError & printMessageControl)) System.out.println(String.format("WinReceiver:Rcv ACT error seq number rx=%d,expected=%d",seq,act.ReceiveSeq));
				act.ReceiveSeq=seq; 
			}
			//System.out.println(String.format("Rcv seq %d",seq));
			//SeqManageAct.put(adr,act);
			checkACTAck(pac,act);
		}
		return NOERROR;
	}
	/**
	 * WIN A0 のLANフレームのデータ長とWINパケット長の整合を検査する
	 * @param data
	 * @param lanlen
	 * @return
	 */
	private boolean checkPacketLength(byte[] data,int lanlen){
		int paclenindex = 3;
		int packlen = unsigned_byte.convShort(data[paclenindex]) * 0x100 + unsigned_byte.convShort(data[paclenindex+1]);
		//int framelen = packlen  + 3;	// control + sub-control + A0 + paclen;
		lanlen -= 3;
		// 1つのLANフレームに複数のWINパケット（パケット以降）が含まれるケース
		while(lanlen>0){
			lanlen -= packlen;
			paclenindex += packlen;
			packlen = unsigned_byte.convShort(data[paclenindex]) * 0x100 + unsigned_byte.convShort(data[paclenindex+1]);
		}
		if(lanlen==0)return true;
		return false;
	}
	// A0パケット受信処理
	private int checkA0Packet(DatagramPacket pac){
		int err = NOERROR;
		
		byte[] data = pac.getData();
		if(unsigned_byte.convShort(data[2]) != 0xa0){
			monitor.winA0UdpRecvErrorCounter++;
			if(0!=(showProtocolError & printMessageControl)) System.out.println(String.format("WinReceiver:Rcv winA0 Error data type=%xH",unsigned_byte.convShort(data[2])));
			return IRREGALHEADER;	// header is not A0
		}
//		int packlen = unsigned_byte.convShort(data[3]) * 0x100 + unsigned_byte.convShort(data[4]);
//		int framelen = packlen  + 3;	// control + sub-control + A0 + paclen;
//		int datalen = pac.getLength();
//		if(framelen != datalen){
//			monitor.winA0UdpRecvErrorCounter++;
//			if(0!=(showProtocolError & printMessageControl)) System.out.println(String.format("WinReceiver:Rcv winA0 Error data length framelen=%d datalen=%d",framelen,datalen));
//			return IRREGALLENGTH;	// illegal length;
//		}
		if(!checkPacketLength(data,pac.getLength())){
			monitor.winA0UdpRecvErrorCounter++;
			if(0!=(showProtocolError & printMessageControl)) System.err.println(String.format("WinReceiver:Rcv winA0 Error data length"));
			return IRREGALLENGTH;	// illegal length;
		}
		InetAddress adr = pac.getAddress();
		int			port = pac.getPort();
		int sq = (int)unsigned_byte.convShort(data[0]);
		int ssq = (int)unsigned_byte.convShort(data[1]);
		//System.out.println(String.format("Recv %d:%d",sq,ssq));
		monitor.winA0UdpRecvCounter++;
		if(!SeqManage.containsKey(adr)){
			if(++sq>0xff) sq=0;
			//System.out.println(String.format("Recv accept %d",sq));
			SeqManage.put(adr,sq);
		}
		else{
			int sqold = SeqManage.get(adr);
			//System.out.println(String.format("SqOld %d",sqold));
			int sqexpect = sqold;
			if(++sqold > 0xff){
				sqold = 0;
			}
			SeqManage.put(adr,sqold);
			if(sq != ssq){	// this is retry packet
				//System.out.println(String.format("this is Retry packet %d,%d",sq,ssq));
				monitor.winA0UdpRetryPacketRecvCounter++;
				return NOERROR;
			}
			if(sqexpect != sq){	// not continuum sequence number 
				//System.out.println(String.format("not continuum %d,%d",sq,sqexpect));
				int delta;
				if(sq > sqexpect)  delta = sq - sqexpect;
				else			return NOERROR;
				if(0!=(showSeqError & printMessageControl)) System.out.println(String.format("WinReceiver:Rcv ACT error seq number rx=%d,expected=%d",sq,sqexpect));
				if(delta >= 128){
					if(++sq>0xff) sq=0;
					//System.out.println(String.format("Preset too far  %d",sq));
					monitor.winA0UdpRetryAbortCounter++;
					SeqManage.put(adr,sq);
					return NOERROR;
				}
				else{
					for(int i=0;i<delta;i++){
						byte[] r = new byte[1];
						//r[0] = byteUtil.convUByte(sqold);
						r[0] = byteUtil.convUByte(sqexpect);
						DatagramPacket resend = new DatagramPacket(r, 1,adr,port);
						try {
							socket.send(resend);
							//System.out.println("SendRetry");
							monitor.winA0UdpSendRetryCounter++;
							//if(0!=(showAckOrNack & printMessageControl)) System.out.println(String.format("WinReceiver:Send winA0 Nack seq=%d",byteUtil.convUByte(sqold)));
							if(0!=(showAckOrNack & printMessageControl)) System.out.println(String.format("WinReceiver:Send winA0 Nack seq=%d",byteUtil.convUByte(sqexpect)));
							//wait(1);
						} catch (IOException e) {
							e.printStackTrace();
						} 
//						catch (InterruptedException e) {
//							e.printStackTrace();
//						}
						//if(++sqold > 0xff) sqold = 0;
						if(++sqexpect > 0xff) sqexpect = 0;
										}
					//SeqManage.put(adr,sq); // 2011/2/25 
					if(++sqexpect>0xff) sqexpect=0;
					//if(++sqexpect>0xff) sqexpect=0;
					//System.out.println(String.format("NextExpect %d",sqexpect));
					SeqManage.put(adr,sqexpect);
				}
				return NOERROR;	// have sent retry request 
			}
		}
		return err;
	}
	public void run(){
		if ( socket	==	null ){
			return;
		}
		try	{
	    	//setPriority(MAX_PRIORITY);
			//socket = new DatagramSocket();
		    socket.setSoTimeout(5*1000);	// 5sec
		    while(!Terminate){
		    	try{
				byte[] ReceiveBuffer = new byte[1024*8];
			    DatagramPacket pac = new DatagramPacket(ReceiveBuffer, ReceiveBuffer.length);
		    	socket.receive(pac); 
		    	if(pac.getLength()>0){
		    		//synchronized(List){
		    		//	List.add(pac);
		    		int err = checkPacket(pac);
		    		if(0 == err){
			    		if(Parent != null) {
		    				Parent.onRecieve(pac);
		    			}
		    		}
		    		else {
			    		if(err == WIN_A8_HEADER || err == WIN_A9_HEADER){
			    			// なにもしない
			    		}
			    		else{
			    			if(Parent != null) {
			    				Parent.onError(pac,err);
				    		}
		    			}
		    		}
		    		//}
		    	}
		    	}catch(Exception e){
	    			if(Parent != null) {
	    				Parent.onTimeout();
	    			}
		    	}
		    }
		    socket.close();
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
	public String getStatusString(){
		return monitor.getMonitorString();
	}
	
	
}	
